package tech.mhuang.pacebox.springboot.autoconfiguration.trace.servlet;

import com.alibaba.fastjson.JSON;
import io.opentracing.Span;
import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator;
import io.opentracing.tag.Tags;
import lombok.extern.slf4j.Slf4j;
import tech.mhuang.pacebox.core.exception.ExceptionUtil;
import tech.mhuang.pacebox.core.io.IOUtil;
import tech.mhuang.pacebox.core.util.StringUtil;
import tech.mhuang.pacebox.springboot.core.servlet.WebRequestStreamHeader;
import tech.mhuang.pacebox.springboot.core.servlet.WebResponseStreamHeader;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * servlet 埋点处理
 *
 * @author mhuang
 * @since 1.0.0
 */
@Slf4j
public class TraceServletFilterSpanDecorator implements ServletFilterSpanDecorator {

    @Override
    public void onRequest(HttpServletRequest httpServletRequest, Span span) {
        Tags.COMPONENT.set(span, "java-web-servlet");
        Tags.HTTP_METHOD.set(span, httpServletRequest.getMethod());
        Tags.HTTP_URL.set(span, httpServletRequest.getRequestURL().toString());
        String queryParams = "";
        if (StringUtil.isNotEmpty(httpServletRequest.getQueryString())) {
            try {
                queryParams = URLDecoder.decode(httpServletRequest.getQueryString(), Charset.defaultCharset().name());
            } catch (UnsupportedEncodingException e) {
            }
        }
        span.setTag("request.cookie", JSON.toJSONString(httpServletRequest.getCookies()));
        span.setTag("request.param", queryParams);
        String requestBody = "";
        if (httpServletRequest instanceof WebRequestStreamHeader) {
            try {
                requestBody = IOUtil.toString(httpServletRequest.getInputStream(), httpServletRequest.getCharacterEncoding());
            } catch (IOException e) {
                log.error("获取流失败", e);
            }
        }
        span.setTag("request.body", requestBody);
        span.setTag("request.header", JSON.toJSONString(getHeadersInfo(httpServletRequest)));
    }

    private Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    @Override
    public void onResponse(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Span span) {
        Tags.HTTP_STATUS.set(span, httpServletResponse.getStatus());
        if (httpServletResponse instanceof WebResponseStreamHeader) {
            WebResponseStreamHeader httpResponse = (WebResponseStreamHeader) httpServletResponse;
            String content = new String(httpResponse.getContentAsByteArray());
            span.setTag("response", content);
        }
    }

    @Override
    public void onError(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Throwable exception, Span span) {
        Tags.ERROR.set(span, Boolean.TRUE);
        span.log(logsForException(exception));

        if (httpServletResponse.getStatus() == HttpServletResponse.SC_OK) {
            // exception is thrown in filter chain, but status code is incorrect
            Tags.HTTP_STATUS.set(span, 500);
        }
    }

    @Override
    public void onTimeout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long timeout, Span span) {
        Map<String, Object> timeoutLogs = new HashMap<>(2);
        timeoutLogs.put("event", "timeout");
        timeoutLogs.put("timeout", timeout);
        span.log(timeoutLogs);
    }

    private Map<String, String> logsForException(Throwable throwable) {
        Map<String, String> errorLog = ExceptionUtil.logsForException(throwable);
        errorLog.put("event", Tags.ERROR.getKey());
        return errorLog;
    }
}
